/**
 * Project Name:service-account
 * File Name:Bootstrap.java
 * Package Name:com.smartlazy.live.service.account.startup
 * Date:2016年12月14日下午11:18:52
 * Author：howepeng
 * Copyright (c) 2016, smartlazy All Rights Reserved.
 *
 */

package com.smartlazy.live.service.account.startup;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.security.AccessControlException;
import java.util.Properties;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * ClassName:Bootstrap <br/>
 * Function: TODO ADD FUNCTION. <br/>
 * Reason:   TODO ADD REASON. <br/>
 * Date:     2016年12月14日 下午11:18:52 <br/>
 * @author   howepeng
 * @version
 * @since    JDK 1.7
 * @see
 */
public class Bootstrap {

    private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);

    private static Bootstrap daemon = null;
    // 是否阻塞
    protected boolean await = true;
    // 关闭端口
    private int port = 9876;
    // 主机地址
    private String address = "localhost";
    // 系统阻塞socket
    private volatile ServerSocket awaitSocket = null;
    // 是否停止等待
    private volatile boolean stopAwait = false;
    // 关闭系统指令
    private String shutdown = "SHUTDOWN";
    // 配置文件列表
    protected String[] configFileNameLst;
    //
    protected String[] configFileLst;
    // logback配置文件
    protected String logbackFile = "../conf/logback.xml";
    // 项目配置文件
    protected String confFile = "../conf/conf.properties";

    private Random random = null;

    private Properties props;


    /**
     *
     * init:初始化 . <br/>
     * TODO(这里描述这个方法适用条件 – 可选).<br/>
     * TODO(这里描述这个方法的执行流程 – 可选).<br/>
     * TODO(这里描述这个方法的使用方法 – 可选).<br/>
     * TODO(这里描述这个方法的注意事项 – 可选).<br/>
     *
     * @author howepeng
     * @throws JoranException
     * @since JDK 1.7
     */
    private void init() throws JoranException {
        // 加载logback配置文件
        File file = new File(logbackFile);
        if (file.exists()) {
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(lc);
            lc.reset();
            configurator.doConfigure(file);
            StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
        }

        file = new File(confFile);
        if (file.exists()) {
            props = new Properties();
            FileInputStream istream = null;
            try {
                istream = new FileInputStream(confFile);
                props.load(istream);
                this.port = Integer.parseInt(props.getProperty("redirectPort"));
                this.address = props.getProperty("redirectAddress");
                istream.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (istream != null) {
                    try {
                        istream.close();
                    } catch (InterruptedIOException ignore) {
                        Thread.currentThread().interrupt();
                    } catch (Throwable ignore) {
                    }

                }
            }
        }

        // 初始化启动文件名称列表(开发使用)
        configFileNameLst = new String[] {
                "applicationContext.xml",
                "datasource-context.xml",
                "mybatisConfig.xml"};
        // 初始化启动文件路径列表(发布使用)
        configFileLst = new String[configFileNameLst.length];
        for (int i = 0; i < configFileNameLst.length; i++) {
            configFileLst[i] = "../conf/" + configFileNameLst[i];
        }
    }

    /**
     *
     * start:服务开始. <br/>
     * TODO(这里描述这个方法适用条件 – 可选).<br/>
     * TODO(这里描述这个方法的执行流程 – 可选).<br/>
     * TODO(这里描述这个方法的使用方法 – 可选).<br/>
     * TODO(这里描述这个方法的注意事项 – 可选).<br/>
     *
     * @author howepeng
     * @throws Exception
     * @since JDK 1.7
     */
    private void start() throws Exception {
        logger.info("------------------------Server startup------------------------");
        logger.info("Server path:" + System.getProperty("user.dir"));
        long t1 = System.nanoTime();
        // spring容器
        AbstractApplicationContext context = null;
        // 判断是本地调试启动还是正式启动服务
        if (existsAll(configFileLst)) {
            logger.info("startup with release");
            context = new FileSystemXmlApplicationContext(configFileLst);
        } else {
            logger.info("startup with Development");
            context = new ClassPathXmlApplicationContext(configFileNameLst);
        }
        // 启动spring容器
        context.start();
        long t2 = System.nanoTime();
        logger.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        if (await) {
            await();
        }
        context.close();
        logger.info("------------------------Server shutdown------------------------");
    }

    /**
     *
     * stop:服务结束. <br/>
     * TODO(这里描述这个方法适用条件 – 可选).<br/>
     * TODO(这里描述这个方法的执行流程 – 可选).<br/>
     * TODO(这里描述这个方法的使用方法 – 可选).<br/>
     * TODO(这里描述这个方法的注意事项 – 可选).<br/>
     *
     * @author howepeng
     * @since JDK 1.7
     */
    private void stop() {
        try (Socket socket = new Socket(address, port); OutputStream stream = socket.getOutputStream()) {
            String shutdown = this.shutdown;
            for (int i = 0; i < shutdown.length(); i++) {
                stream.write(shutdown.charAt(i));
            }
            stream.flush();
        } catch (ConnectException ce) {
            logger.error("stopServer.connectException [" + address + ":" + port + "]", ce);
            ce.printStackTrace();
            System.exit(1);
        } catch (IOException e) {
            logger.error("stopServer.IOException", e);
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     *
     * await:等候. <br/>
     * TODO(这里描述这个方法适用条件 – 可选).<br/>
     * TODO(这里描述这个方法的执行流程 – 可选).<br/>
     * TODO(这里描述这个方法的使用方法 – 可选).<br/>
     * TODO(这里描述这个方法的注意事项 – 可选).<br/>
     *
     * @author howepeng
     * @since JDK 1.7
     */
    private void await() {
        try {
            awaitSocket = new ServerSocket(port, 1, InetAddress.getByName(address));
        } catch (IOException e) {
            logger.error("await: create[" + address + ":" + port + "]: ", e);
            return;
        }
        try {

            while (!stopAwait) {
                ServerSocket serverSocket = awaitSocket;
                if (serverSocket == null) {
                    break;
                }

                Socket socket = null;
                StringBuilder command = new StringBuilder();
                try {
                    InputStream stream;
                    long acceptStartTime = System.currentTimeMillis();
                    try {
                        socket = serverSocket.accept();
                        socket.setSoTimeout(10 * 1000);
                        stream = socket.getInputStream();
                    } catch (SocketTimeoutException ste) {
                        logger.error(
                                "Provider.accept.timeout:" + Long.valueOf(System.currentTimeMillis() - acceptStartTime),
                                ste);
                        continue;
                    } catch (AccessControlException ace) {
                        logger.error("Provider.accept security exception: " + ace.getMessage(), ace);
                        continue;
                    } catch (IOException e) {
                        if (stopAwait) {
                            break;
                        }
                        logger.error("Provider.await: accept: ", e);
                        break;
                    }

                    // 读取
                    int expected = 1024;
                    while (expected < shutdown.length()) {
                        if (random == null)
                            random = new Random();
                        expected += (random.nextInt() % 1024);
                    }
                    while (expected > 0) {
                        int ch = -1;
                        try {
                            ch = stream.read();
                        } catch (IOException e) {
                            ch = -1;
                        }
                        if (ch < 32)
                            break;
                        command.append((char) ch);
                        expected--;
                    }
                } finally {
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        // Ignore
                    }
                }

                boolean match = command.toString().equals(shutdown);
                if (match) {
                    break;
                } else {

                }
            }
        } finally {
            ServerSocket serverSocket = awaitSocket;
            awaitSocket = null;

            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        if (daemon == null) {
            Bootstrap bootstrap = new Bootstrap();
            daemon = bootstrap;
        }

        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("start")) {
                daemon.init();
                daemon.start();
            } else if (command.equals("stop")) {
                daemon.init();
                daemon.stop();
            } else {
            }

            // System.in.read(); // 按任意键退出

        } catch (Throwable t) {
            if (t instanceof InvocationTargetException && t.getCause() != null) {
                t = t.getCause();
            }
            t.printStackTrace();
            System.exit(1);
        }
    }


    /**
     *
     * existsAll:判断所有配置文件是否存在. <br/>
     *
     * @author howepeng
     * @param configFileLst
     * @return 所有配置文件存在的时候返回true，其他时候返回false
     * @since JDK 1.7
     */
    private boolean existsAll(String[] configFileLst) {
        if (configFileLst == null || configFileLst.length <= 0) {
            return false;
        }
        for (String item : configFileLst) {
            logger.info("file:"+item);
            File file =new File(item);
            if(!file.exists()) {
                logger.info("不存在");
                return false;
            }
        }
        return true;
    }
}
